package pl.put.augrelshooter.activities;

import java.net.InetAddress;
import java.util.List;

import org.opencv.android.BaseLoaderCallback;
import org.opencv.android.CameraBridgeViewBase;
import org.opencv.android.CameraBridgeViewBase.CvCameraViewListener;
import org.opencv.android.LoaderCallbackInterface;
import org.opencv.android.OpenCVLoader;
import org.opencv.core.Core;
import org.opencv.core.CvType;
import org.opencv.core.Mat;
import org.opencv.core.Point;
import org.opencv.core.Scalar;
import org.opencv.core.Size;
import org.opencv.highgui.Highgui;
import org.opencv.imgproc.Imgproc;

import pl.put.augrelshooter.R;
import pl.put.augrelshooter.connection.GameConnection;
import pl.put.augrelshooter.connection.NsdHelper;
import pl.put.augrelshooter.dataproviders.GameDataProvider;
import pl.put.augrelshooter.logic.detection.IDetectable;
import pl.put.augrelshooter.logic.detection.ObjectsDetector;
import pl.put.augrelshooter.logic.detection.opencv.DetectedObject;
import pl.put.augrelshooter.logic.game.Game;
import pl.put.augrelshooter.logic.game.IGameEventsListener;
import pl.put.augrelshooter.logic.game.Player;
import pl.put.augrelshooter.logic.game.badges.AllBadges;
import pl.put.augrelshooter.logic.game.badges.Badge;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.content.Intent;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.util.Log;
import android.view.Display;
import android.view.View;
import android.widget.ImageButton;
import android.widget.TextView;
import android.widget.Toast;

public class GameActivity extends Activity implements CvCameraViewListener, IGameEventsListener {
	private static final String TAG = "AugRel Game Activity";

	static {
		if (!OpenCVLoader.initDebug()) {
			Log.e(TAG, "Can't load OpenCV");
		}
	}

	private BaseLoaderCallback mLoaderCallback = new BaseLoaderCallback(this) {
		@Override
		public void onManagerConnected(int status) {
			switch (status) {
			case LoaderCallbackInterface.SUCCESS: {
				Log.i(TAG, "OpenCV loaded successfully");
				mOpenCvCameraView.enableView();
			}
				break;
			default: {
				super.onManagerConnected(status);
			}
				break;
			}
		}
	};

	private Mat frame;
	private Mat crosshair;
	private Mat crosshairMask;
	private Game game;
	private GameDataProvider dataProvider;
	private ObjectsDetector detector;
//	private ColorBlobDetector colorDetector;
	private double[] centerOfDisplay;
	private NsdHelper nsdHelper;
	private Handler updateHandler;
	private GameConnection connection;
	private int isServer;
	private int isDead;

	private CameraBridgeViewBase mOpenCvCameraView;

	// ACTIVITY METHODS----------------------------------------------------------------------------------------

	@SuppressWarnings("deprecation")
	@SuppressLint("NewApi")
	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_game_screen);
		isServer = getIntent().getExtras().getInt("isServer");

		enableCommunication();
		dataProvider = new GameDataProvider(this);

		if (isServer == 1)
			prepareGame();
		else
			prepareGameClient();
		updateHealthLabel();
		updateAmmoLabel();
		updateWeaponIcon();

		detector = new ObjectsDetector();
//		colorDetector = new ColorBlobDetector();

		Display display = getWindowManager().getDefaultDisplay();
		int width = display.getWidth();
		int height = display.getHeight();

		centerOfDisplay = new double[2];
		centerOfDisplay[0] = width / 2;
		centerOfDisplay[1] = height / 2;

		Bitmap crosshairBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.crosshair);
		crosshair = detector.getMatFromBitmap(crosshairBitmap);
		crosshairMask = new Mat();

		mOpenCvCameraView = (CameraBridgeViewBase) findViewById(R.id.tutorial2_activity_surface_view);
		mOpenCvCameraView.setCvCameraViewListener(this);
	}

	@Override
	protected void onStop() {
		if (isServer == 1)
			nsdHelper.tearDown();
		if (mOpenCvCameraView != null)
			mOpenCvCameraView.disableView();
		connection.tearDown();
		super.onStop();
	}

	@Override
	public void onResume() {
		super.onResume();
		OpenCVLoader.initAsync(OpenCVLoader.OPENCV_VERSION_2_4_3, this, mLoaderCallback);
	}

	@Override
	public void onPause() {
		super.onPause();
	}

	// OPENCV METHODS-------------------------------------------------------------------------------------------------------------------

	@Override
	public void onCameraViewStarted(int width, int height) {
		frame = new Mat(height, width, CvType.CV_8UC4);
	}

	@Override
	public void onCameraViewStopped() {
		frame.release();
	}

	@Override
	public Mat onCameraFrame(Mat inputFrame) {
		Core.flip(inputFrame, frame, 1);

		int originalWidth = frame.width();
		int originalHeight = frame.height();
		Imgproc.resize(frame, frame, new Size(originalWidth * 0.8, originalHeight * 0.8));
		Imgproc.resize(frame, frame, new Size(originalWidth, originalHeight));

		Imgproc.erode(frame, frame, new Mat());
		Imgproc.dilate(frame, frame, new Mat());

		if (isDead == 0) {
			detectObjects(inputFrame);
			drawCrosshair(frame);
//			colorDetector.setColorRadius(ColorDescriptionFactory.obtainDescriptionFor(ColorDescriptionsEnum.RED).getRadius());
//			colorDetector.setHsvColor(ColorDescriptionFactory.obtainDescriptionFor(ColorDescriptionsEnum.RED).getColor());
//
//			colorDetector.process(frame);
//			Imgproc.resize(colorDetector.getColorRangeMask(), frame, new Size(originalWidth, originalHeight));
		}

		return frame;
	}

	private void drawCrosshair(Mat inputFrame) {
		int crosshairTop = (int) (inputFrame.height() / 2 - (int) crosshair.height() / 2);
		int crosshairBottom = crosshairTop + crosshair.height();
		int crosshairLeft = (int) (inputFrame.width() / 2 - (int) crosshair.width() / 2);
		int crosshairRight = crosshairLeft + crosshair.width();

		Imgproc.cvtColor(crosshair, crosshairMask, Imgproc.COLOR_RGB2GRAY);
		Core.inRange(crosshairMask, Scalar.all(0), Scalar.all(200), crosshairMask);

		crosshair.copyTo(inputFrame.colRange(crosshairLeft, crosshairRight).rowRange(crosshairTop, crosshairBottom), crosshairMask);
	}

	private void detectObjects(Mat inputFrame) {
		for (IDetectable detectable : dataProvider.getAllDetectables()) {
			List<DetectedObject> detected = null;
			detector.detectOnScene(inputFrame, detectable.getBadge());
			detected = detector.getDetectedObjects();
			for (DetectedObject detectedObject : detected) {
				detectedObject.setDetectable(detectable);

				Point topLeft = new Point(detectedObject.getCenter().x - detectedObject.getSize() / 2, detectedObject.getCenter().y - detectedObject.getSize() / 2);
				Point bottomRight = new Point(detectedObject.getCenter().x + detectedObject.getSize() / 2, detectedObject.getCenter().y + detectedObject.getSize() / 2);
 
				Scalar color = new Scalar(0, 255, 0);

				Point labelPoint = new Point(detectedObject.getCenter().x - detectedObject.getSize() / 2, detectedObject.getCenter().y - detectedObject.getSize() / 2 - 15);

				Core.rectangle(frame, topLeft, bottomRight, color);
				Core.putText(frame, detectable.getLabel(), labelPoint, Core.FONT_HERSHEY_COMPLEX, 1.0, color);
			}
		}
	}

	// COMMUNICATION-------------------------------------------------------------------------------------------------------------------------------------

	@SuppressLint("HandlerLeak")
	private void enableCommunication() {

		updateHandler = new Handler() {
			@Override
			public void handleMessage(Message data) {
				String msg = data.getData().getString("msg");
				String[] parts = msg.split(";");

				if (parts[0].equals("GET_NAME")) {
					sendGameDetails();
				} else if (parts[0].equals("PLAYER")) {
					addEnemyPlayer(parts[1], parts[2]);
				} else if (parts[0].equals("HIT")) {
					game.playerWasHit(Integer.parseInt(parts[1]));
				} else if (parts[0].equals("WINNER")) {
					playerWon();
				} else {
					showToast(msg);
				}
			}
		};

		connection = new GameConnection(updateHandler, isServer);

		if (isServer == 1) {
			nsdHelper = new NsdHelper(this);
			nsdHelper.initializeNsd(isServer);
			nsdHelper.registerService(getIntent().getExtras().getString("game_name"), connection.getPort());
		} else {
			int port = getIntent().getExtras().getInt("port");
			InetAddress address = (InetAddress) getIntent().getExtras().getSerializable("host");
			connection.connectToServer(address, port);
		}
	}

	private void sendGameDetails() {
		String sMsg = "POST_GAME;";
		sMsg += game.getGameName() + ";";
		sMsg += getIntent().getExtras().getString("player_color") + ";";
		sMsg += game.getPlayer().getLabel() + ";";
		sMsg += getIntent().getExtras().getString("player_color") + ";";
		connection.sendMessage(sMsg);
	}

	protected void showToast(String text) {
		Toast.makeText(this, text, Toast.LENGTH_SHORT).show();
	}

	// INITIALIZATION-------------------------------------------------------------------------------------------

	private void prepareGame() {
		String gameName = getIntent().getExtras().getString("game_name");
		String playerName = getIntent().getExtras().getString("player_name");
		String badgeName = getIntent().getExtras().getString("player_color");

		Badge playerBadge = AllBadges.ALL_BADGES.get(badgeName);
		Player player = new Player(playerName, playerBadge);

		isDead = 0;
		game = new Game(gameName, player, this);
	}

	private void prepareGameClient() {
		String gameName = getIntent().getExtras().getString("game_name");
		String playerName = getIntent().getExtras().getString("player_name");
		String badgeName = getIntent().getExtras().getString("player_color");
		String enemyName = getIntent().getExtras().getString("enemy_name");
		String enemyColor = getIntent().getExtras().getString("enemy_color");

		Badge playerBadge = AllBadges.ALL_BADGES.get(badgeName);
		Player player = new Player(playerName, playerBadge);

		isDead = 0;
		game = new Game(gameName, player, this);

		addEnemyPlayer(enemyName, enemyColor);
	}

	// GAME LOGIC------------------------------------------------------------------------------------------------

	private void addEnemyPlayer(String name, String badge) {
		Badge enemyBadge = AllBadges.ALL_BADGES.get(badge);
		Player enemy = new Player(name, enemyBadge);

		newPlayerJoined(enemy);
	}

	public void shootButtonPressed(View v) {
		IDetectable target = null;

		if (detector.getDetectedObjects() != null) {
			for (DetectedObject object : detector.getDetectedObjects()) {
				if (Math.abs(object.getCenter().x - centerOfDisplay[0]) < object.getSize() && Math.abs(object.getCenter().y - centerOfDisplay[1]) < object.getSize()) {
					target = object.getDetectable();
					break;
				}
			}
		}

		game.playerWantsToShot(target);
	}

	private void updateHealthLabel() {
		TextView healthLabel = (TextView) findViewById(R.id.lifeLabel);
		healthLabel.setText(Integer.toString(game.getPlayer().getHealthPoints()));
	}

	private void updateAmmoLabel() {
		TextView ammoLabel = (TextView) findViewById(R.id.ammoLabel);
		ammoLabel.setText(Integer.toString(game.getPlayer().getWeapon().getAmmo()));
	}

	private void updateWeaponIcon() {
		ImageButton fireButton = (ImageButton) findViewById(R.id.fireButton);
		fireButton.setImageResource(game.getPlayer().getWeapon().getIconIdentifier());
	}

	@Override
	public void healthLevelChanged() {
		updateHealthLabel();
	}

	@Override
	public void ammoLevelChanged() {
		updateAmmoLabel();
	}

	@Override
	public void weaponChanged() {
		updateWeaponIcon();
		updateAmmoLabel();
	}

	@Override
	public void newPlayerJoined(IDetectable player) {
		dataProvider.getAllDetectables().add(player);
	}

	@Override
	public void playerIsDead() {
		mOpenCvCameraView.SetCaptureFormat(Highgui.CV_CAP_ANDROID_GREY_FRAME);
		isDead = 1;
		connection.sendMessage("WINNER");
		Intent intent = new Intent(GameActivity.this, DialogActivity.class);
		intent.putExtra("won", 1);
		startActivity(intent);
	}
	
	public void playerWon() {
		mOpenCvCameraView.SetCaptureFormat(Highgui.CV_CAP_ANDROID_GREY_FRAME);
		isDead = 1;
		Intent intent = new Intent(GameActivity.this, DialogActivity.class);
		intent.putExtra("won", 1);
		startActivity(intent);
	}

	@Override
	public void enemyHit(String dmg) {
		connection.sendMessage("HIT;" + dmg);
	}

	public void prevWeaponClick(View v) {
		game.previousWeapon();
	}

	public void nextWeaponClick(View v) {
		game.nexWeapon();
	}

}
